﻿/*
 *  Atención programador curiosin
 *  *****************************
 *  1) Cuando este código no encuentra ciertos métodos o no compila se
 *     debe cambiar la configuración de soluciones a "net_2_0_Debug" o
 *     "net_2_0_Release" en vez de Debug o Release.
 *  2) Se debe poner a este proyecto como proyecto inicial...
 *  3) Se deben poner los parametros de línea de comandos en
 *     "proyecto/propiedades/depurar/argumentos-de-linea-de-comandos"
 *     hay que pegarlos con cuidado ya que a veces quedan espacios
 *     vacíos entre ellos.
 */

using System;
using System.Collections.Generic;
using System.IO;
using SR = System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;

namespace AgregarTrace
{
    class Program
    {
        static string tracerPath = string.Empty;

        static void Main (string [] args)
        {
            /*  por ejemplo...
                "..\..\..\ParasiTracer\ParasiTracerCF\bin\Debug\ParasiTracer.dll"
                "C:\FuentesCSharp\TecnoBusPortable\TecnoBus.Presentacion\bin\Debug\ParasiOut"
                "C:\FuentesCSharp\TecnoBusPortable\TecnoBus.Presentacion\bin\Debug\TecnoBus.Presentacion.exe"
                "C:\FuentesCSharp\TecnoBusPortable\TecnoBus.Presentacion\bin\Debug\TecnoBus.Comun.dll"
                "C:\FuentesCSharp\TecnoBusPortable\TecnoBus.Presentacion\bin\Debug\TecnoBus.Comunicacion.dll"
                "C:\FuentesCSharp\TecnoBusPortable\TecnoBus.Presentacion\bin\Debug\TecnoBus.Dispositivos.dll"
             */

            tracerPath = args[0];

            if (!File.Exists(tracerPath))
                throw new Exception("el path del tracer no existe");

            var dirOut = args[1];
            var fTablePath = Path.Combine(dirOut, "ftable.txt");
            var assemblyPaths = new List<string>();

            for (int i = 2; i < args.Length; i++)
                assemblyPaths.Add(args[i]);

            if (! Directory.Exists(dirOut))
                Directory.CreateDirectory(dirOut);

            if (File.Exists(fTablePath))
                File.Delete(fTablePath);

            var resolver = new DefaultAssemblyResolver();
            resolver.AddSearchDirectory (
                @"C:\FuentesCSharp\TecnoBusPortable\TecnoBus.Presentacion\bin\Debug\"
            );

            var readerParameters = new ReaderParameters { 
                AssemblyResolver = resolver
            };

            int methodId = 0;
            foreach (string assemblyPath in assemblyPaths) {
                var assembly = AssemblyDefinition.ReadAssembly(
                    assemblyPath, readerParameters
                );

                var partialFuncTable = transformAssembly (assembly, ref methodId);

                using (StreamWriter sw = File.AppendText(fTablePath))
                {
                    foreach (FuncTableFileItem item in partialFuncTable)
                        sw.WriteLine(item.ToString());

                    sw.Close();
                }

                assembly.Write (
                    Path.Combine (
                        dirOut, Path.GetFileName(assemblyPath)));
            }

            // copio el tracer a la carpeta "out"
            File.Copy(
                tracerPath,
                Path.Combine (dirOut, Path.GetFileName(tracerPath)),
                true
            );
        }

        static void transformMethodStart(
            TypeDefinition      type,
            MethodDefinition    md, 
            int                 methodId,
            TracerReferences    tracerReferences
        )
        {
            var worker  = md.Body.GetILProcessor();
            var start   = md.Body.getStart();

            // empujo id del metodo
            md.Body.injectBefore (
                start,
                worker.Create(OpCodes.Ldc_I4, methodId)
            );

            if (md.Parameters.Count == 0)
            {
                md.Body.injectBefore(
                    start,
                    worker.Create(OpCodes.Call, tracerReferences["inFunc"])
                );
            }
            else
            {
                // llamo a inFuncWithParams, empujo un 1, si inFuncWithParams(..) != 1, salto!
                md.Body.injectBefore(
                    start,
                    worker.Create(OpCodes.Call, tracerReferences["inFuncWithParams"]),
                    worker.Create(OpCodes.Ldc_I4_1),
                    worker.Create(OpCodes.Bne_Un, start)
                );

                foreach (ParameterDefinition param in md.Parameters)
                {
                    int paramIndex = param.Index +
                        (md.IsStatic ? 0 : 1);

                    MetadataType paramMetadataType =
                        param.ParameterType.MetadataType;

                    switch (paramMetadataType)
                    {
                        case MetadataType.Boolean:
                        case MetadataType.Char:
                        case MetadataType.SByte:
                        case MetadataType.Byte:
                        case MetadataType.Int16:
                        case MetadataType.UInt16:
                        case MetadataType.Int32:
                        case MetadataType.UInt32:
                        case MetadataType.Int64:
                        case MetadataType.UInt64:
                        case MetadataType.Single:
                        case MetadataType.Double:
                        case MetadataType.String:
                        case MetadataType.Object:
                        case MetadataType.Class:
                        case MetadataType.Array:
                        //case MetadataType.GenericInstance: // <- tira error porque hay que boxear ciertas instancias genericas como System.Nullable ("?") INVESTIGAR!
                            Console.WriteLine("{0} :: {1}", type.Name, md.Name);
                            Console.WriteLine();
                            md.Body.injectBefore(
                                start,
                                worker.createLdargX (paramIndex),
                                worker.Create       (OpCodes.Call, tracerReferences[paramMetadataType])
                            );
                            break;
                        case MetadataType.ValueType:
                            md.Body.injectBefore(
                                start,
                                worker.createLdargX(paramIndex),
                                worker.Create(OpCodes.Box, param.ParameterType),
                                worker.Create(OpCodes.Call, tracerReferences["objectParam"])
                            );
                            break;
                        //case MetadataType.MVar: // T
                        //    ///Console.ForegroundColor = ConsoleColor.Red;
                        //    ///Console.WriteLine(param.ParameterType);
                        //    ///Console.WriteLine(" resolveto-> {0}", param.ParameterType);
                        //    ///Console.WriteLine(" metadatat-> {0}", param.ParameterType.MetadataType);
                        //    break;
                        //case MetadataType.ByReference:
                        //    /// si es "out" no se que hacer...
                        //    /// (se puede verificar si es out con 
                        //    /// "param.Attributes == ParameterAttributes.Out"
                        //    /// 
                        //    /// cargo con ldind.[opcion]
                        //    /// si su tipo resuelto es un ValueType tengo
                        //    /// que boxearlo...
                        //    ///
                        //    ///ByReferenceType byref = (ByReferenceType)param.ParameterType;
                        //    ///if (byref.ElementType.IsGenericParameter)
                        //    ///{
                        //    ///    Console.ForegroundColor = ConsoleColor.Cyan;
                        //    ///    GenericParameter gp = (GenericParameter)byref.ElementType;
                        //    ///    Console.WriteLine(byref);
                        //    ///    Console.WriteLine("metadatat-> {0}" , byref.MetadataType);
                        //    ///    Console.WriteLine(" resolveto-> {0}", gp);
                        //    ///    Console.WriteLine(" metadatat-> {0}", gp.MetadataType);
                        //    ///}
                        //    ///else
                        //    ///{
                        //    ///    if (byref.ElementType.IsGenericInstance)
                        //    ///    {
                        //    ///        Console.ForegroundColor = ConsoleColor.Yellow;
                        //    ///    }
                        //    ///    else
                        //    ///    {
                        //    ///        Console.ForegroundColor = ConsoleColor.Gray;
                        //    ///    }
                        //    ///    Console.WriteLine(byref);
                        //    ///    TypeReference tipoResuelto = param.ParameterType.Resolve();
                        //    ///    Console.WriteLine(" resolveto-> {0}", tipoResuelto);
                        //    ///}
                        //    break;
                    }
                }
            }
        }

        static void transformMethodReturns(
            TypeDefinition      type,
            MethodDefinition    md,
            int                 methodId,
            TracerReferences    tracerReferences
        )
        {
            var worker = md.Body.GetILProcessor();

            foreach (Instruction @return in md.Body.getRets())
            {
                if (md.ReturnType.MetadataType != MetadataType.Void)
                {
                    // aca informaría el retval, duplicando la pila
                    // con un "dup" y llamando a una función para
                    // informar ...
                }

                md.Body.injectBeforeAndFix(
                    @return,
                    worker.Create(OpCodes.Ldc_I4, methodId),
                    worker.Create(OpCodes.Call, tracerReferences["outFunc"])
                );
            }
        }

        static void transformMethodThrows(
            TypeDefinition      type,
            MethodDefinition    md,
            int                 methodId,
            TracerReferences    tracerReferences
        )
        {
            var worker = md.Body.GetILProcessor();

            foreach (Instruction @throw in md.Body.getThrows())
            {
                md.Body.injectBeforeAndFix(
                    @throw,
                    worker.Create(OpCodes.Ldc_I4, methodId),
                    worker.Create(OpCodes.Call, tracerReferences["throwFunc"])
                );
            }
        }

        static List<FuncTableFileItem> transformAssembly(
            AssemblyDefinition  assemblyDefinition,
            ref int             methodId
        )
        {
            var ret             = new List<FuncTableFileItem>();
            var tracerReferences= new TracerReferences(
                tracerPath, assemblyDefinition
            );

            foreach (TypeDefinition type in assemblyDefinition.MainModule.Types)
            {
                foreach (MethodDefinition md in type.Methods)
                {
                    if (md.HasBody)
                    {
                        md.Body.SimplifyMacros  ();
                        transformMethodStart    (type, md, methodId, tracerReferences);
                        transformMethodReturns  (type, md, methodId, tracerReferences);
                        transformMethodThrows   (type, md, methodId, tracerReferences);
                        md.Body.OptimizeMacros  ();

                        ret.Add(new FuncTableFileItem(
                            methodId,
                            string.Format("{0}::{1}", type.FullName, md.Name)
                        ));

                        methodId++;
                    } // tiene cuerpo :D
                } // para cada método..
            } // para cada tipo ..

            return ret;
        }
    }
}
